﻿/*
参考文档：https://ai-cyber-security.feishu.cn/docx/Krk0davcQoDQcBx0ZQAc2pOUnTh

C:\Users\hao\source\repos\data-automation-script-library\haowei\args_demo.py

*/
using PythonInvokerTest.Forms;
using PythonInvokerTest.Utils;
using System;
using System.Diagnostics;
using System.IO;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace PythonInvokerTest
{
    public partial class MainForm : Form
    {
        Process process = new Process();
        string logpath = "logs";
        ConfigAccess ca = ConfigAccess.LoadFile("config.ini");

        public MainForm()
        {
            InitializeComponent();
        }


        public void SetTextBoxPath(TextBox tb)
        {
            tb.Text = funs.SelectFile(tb.Text);
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            // TreeView的图片设置为空
            tvDirectory.SelectedImageIndex = -1;

            // 创建日志目录
            if (!Directory.Exists(logpath))
                Directory.CreateDirectory(logpath);

            // 加载路径数据
            ca = ConfigAccess.LoadFile();
            foreach (var path in ca.GetValue("Paths").Split(';'))
            {
                if (Directory.Exists(path))
                    FileTreeBuilder.BuildTreeView(tvDirectory, path);
            }

            if (tvDirectory.Nodes.Count > 0)
            {
                tvDirectory.SelectedNode = tvDirectory.Nodes[0];
                Debug.WriteLine($"IsSelected: {tvDirectory.SelectedNode.IsSelected}");
            }

            // 窗体最大化，文本显示版本号，清空相关显示
            this.WindowState = FormWindowState.Maximized;
            this.Text += $" Ver.{Program.VersionInfo}({Program.ReleaseDate})";
            txtInputFilePath.Clear();
            txtOutputFilePath.Clear();
        }

        private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            string[] paths = new string[tvDirectory.Nodes.Count];
            for (int i = 0; i < paths.Length; i++)
                if (tvDirectory.Nodes[i].Tag is string path && Directory.Exists(path))
                    paths[i] = path;
            ca.SetValue("Paths", string.Join(";", paths));
            ca.Save();
        }


        public void RunPythonScript(string pyscript, string inputfile, string outputfile)
        {
            var task = Task.Run(() =>
            {
                var cur_dir = Path.GetDirectoryName(pyscript);
                //var inputfile_abs = inputfile == Path.GetFullPath(inputfile) ? inputfile : Path.Combine(cur_dir, inputfile);
                //var outputfile_abs = outputfile == Path.GetFullPath(outputfile) ? outputfile : Path.Combine(cur_dir, outputfile);
                //Console.WriteLine("cur dir:" + cur_dir);
                //Console.WriteLine("infile: " + inputfile_abs);
                //Console.WriteLine("outfile:" + outputfile_abs);

                process = new Process();

                process.StartInfo.FileName = "python.exe";
                process.StartInfo.Arguments = $"-u {Path.GetFullPath(pyscript)} {inputfile} {outputfile}";
                if (ckRunInScriptDirectory.Checked)
                    process.StartInfo.WorkingDirectory = cur_dir;
                AddErrorMessage("Running directory: " + process.StartInfo.WorkingDirectory);
                process.StartInfo.UseShellExecute = false;
                process.StartInfo.RedirectStandardOutput = true;
                process.StartInfo.RedirectStandardError = true;
                process.StartInfo.CreateNoWindow = true;
                // process.StartInfo.StandardOutputEncoding = Encoding.UTF8; 会出现乱码
                process.StartInfo.StandardOutputEncoding = Encoding.Default;
                process.OutputDataReceived += Process_OutputDataReceived;
                process.ErrorDataReceived += Process_ErrorDataReceived;
                process.Exited += Process_Exited;

                AddErrorMessage($"Running script: {Path.GetFullPath(pyscript)} {inputfile} {outputfile}");
                try
                {
                    AddErrorMessage("Start");
                    process.Start();
                    process.BeginOutputReadLine();
                    // process.BeginErrorReadLine();
                    bool existed = false;
                    if (numTimeoutSecond.Value == 0)
                        process.WaitForExit();
                    else
                        existed = process.WaitForExit((int)(numTimeoutSecond.Value * 60 * 1000));

                    AddErrorMessage("existed: " + existed);
                    if (process.HasExited)
                    {
                        AddErrorMessage("Existed normally.");
                    }
                    else
                    {
                        process.Kill();
                        AddErrorMessage("Timeout and killed.");
                        Process_Exited(null, null);
                    }

                    RunningTimetimer.Stop();
                    // Thread.Sleep(1000);
                    // Console.WriteLine(process.StandardError.ReadToEnd());
                }
                catch (Exception ex)
                {
                    AddErrorMessage("ex:" + ex.ToString());
                    Process_Exited(null, null);
                }
            });

        }

        private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (!string.IsNullOrEmpty(e.Data))
                AddErrorMessage(e.Data);
        }

        private void Process_Exited(object sender, EventArgs e)
        {
            process.OutputDataReceived -= Process_OutputDataReceived;
            process.ErrorDataReceived -= Process_ErrorDataReceived;
            process.Exited -= Process_Exited;
            AddErrorMessage(process.StandardError.ReadToEnd());
            AddErrorMessage($"Exited({process.ExitCode}). Runtime: {(process.ExitTime - process.StartTime).TotalSeconds:0.000}s");
            btnRun.Enabled = true;
            btnStop.Enabled = false;
            RunningTimetimer.Stop();
            lblRunningState.Text = "执行完成。";


            // 生成日志信息，文件名为 json2csv.py_20240525_221532.log
            string pyfile = Path.GetFileName(txtPythonScriptPath.Text);
            string filename = Path.Combine(logpath, $"{pyfile}_{DateTime.Now:yyyyMMdd_HHmmss}.log");
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("-------------------------------------------------【基本信息】-------------------------------------------------");
            sb.AppendLine($"执行脚本：{txtPythonScriptPath.Text}");
            sb.AppendLine($"输入文件：{txtInputFilePath.Text}");
            sb.AppendLine($"输出文件：{txtOutputFilePath.Text}");
            sb.AppendLine($"退出状态：{process.ExitCode}");
            sb.AppendLine($"执行时间：{(process.ExitTime - process.StartTime).TotalSeconds:0.000}s\r\n");

            sb.AppendLine("-------------------------------------------------【运行输出】-------------------------------------------------");
            sb.AppendLine(txtConsoleLines.Text + "\r\n");

            sb.AppendLine("-------------------------------------------------【运行输出】-------------------------------------------------");
            sb.AppendLine(txtLog.Text);
            File.WriteAllText(filename, sb.ToString(), Encoding.UTF8);


            AddErrorMessage("执行日志已输出至到文件：" + Path.GetFullPath(filename));
        }

        private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (!string.IsNullOrEmpty(e.Data))
                txtConsoleLines.Text += $"{DateTime.Now:yyyy/MM/dd HH:mm:ss.fff} {e.Data}\r\n";
        }

        private void btnTest1_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process p = new System.Diagnostics.Process();
            p.StartInfo.FileName = "cmd.exe";
            p.StartInfo.Arguments = @"/C tree C:\gitee.com\";
            p.StartInfo.UseShellExecute = false; // 必需设置此属性为true，下面两个属性才有效
            p.StartInfo.RedirectStandardOutput = true; // 关键行2
            p.StartInfo.CreateNoWindow = true;
            p.Start();
            txtConsoleLines.Text = p.StandardOutput.ReadToEnd();
        }

        /// <summary>
        /// 异步调用。参见：https://blog.csdn.net/qq_38693757/article/details/127867464
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnRun_Click(object sender, EventArgs e)
        {
            tabControl1.SelectedTab = tpExecution;
            btnRun.Enabled = false;
            btnStop.Enabled = true;
            lblRunningState.Text = "执行中...";
            txtConsoleLines.Clear();
            txtLog.Clear();
            Application.DoEvents();
            RunPythonScript(txtPythonScriptPath.Text, txtInputFilePath.Text, txtOutputFilePath.Text);
            startTime = DateTime.Now;
            RunningTimetimer.Start();
        }

        private void btnSelect1_Click(object sender, EventArgs e)
        {
            SetTextBoxPath(txtPythonScriptPath);
        }

        private void btnSelect2_Click(object sender, EventArgs e)
        {
            SetTextBoxPath(txtInputFilePath);
        }

        private void btnSelect3_Click(object sender, EventArgs e)
        {
            SetTextBoxPath(txtOutputFilePath);
        }

        public void AddErrorMessage(string text)
        {
            txtLog.Text += $"{DateTime.Now:HH:mm:ss.ffff} {tiRunningTime.Text} tid-{Thread.CurrentThread.ManagedThreadId}: {text}\r\n";
        }

        private void btnTest2_Click(object sender, EventArgs e)
        {
            AddErrorMessage("Button Click");

            Task.Run(() =>
            {
                Process p = new System.Diagnostics.Process();
                p.StartInfo.FileName = "python.exe";
                p.StartInfo.Arguments = Path.GetFullPath("sleep_demo.py");
                //p.StartInfo.FileName = "ping.exe";// @"python.exe";
                //p.StartInfo.Arguments = $"baidu.com";
                p.StartInfo.UseShellExecute = false;
                p.StartInfo.RedirectStandardOutput = true;
                p.StartInfo.CreateNoWindow = true;
                AddErrorMessage($"Start.");

                // OutputDataReceived event official documentation: https://learn.microsoft.com/zh-cn/dotnet/api/system.diagnostics.process.outputdatareceived?view=net-8.0&redirectedfrom=MSDN
                p.OutputDataReceived += (s1, e1) =>
                {
                    AddErrorMessage($"OutputDataReceived={e1.Data}");
                    if (!String.IsNullOrEmpty(e1.Data))
                    {
                        txtConsoleLines.Text += e1.Data + "\r\n";
                        Application.DoEvents();
                    }
                };

                p.Exited += (s2, e2) =>
                {
                    txtConsoleLines.Text += $"Existed. Exit Code: {(s2 as Process).ExitCode}\r\n";
                    Application.DoEvents();
                    AddErrorMessage($"Exited.");
                };

                p.Start();
                p.BeginOutputReadLine();
                p.WaitForExit(10 * 1000); // 等待5秒
                if (!p.HasExited)
                {
                    txtConsoleLines.Text += "Timeout：Killed process.\r\n";
                    p.Kill();
                }
                txtConsoleLines.Text += $"Done({p.ExitCode})\r\n";
                Application.DoEvents();
            });
        }

        private void btnTest3_Click(object sender, EventArgs e)
        {

        }

        private void btnStop_Click(object sender, EventArgs e)
        {
            lblRunningState.Text = "强制退出";
            try
            {
                process.Kill();
                AddErrorMessage("强制退出");
            }
            catch (Exception ex)
            {
                AddErrorMessage(ex.Message);
                lblRunningState.Text += ex.GetType().Name;
            }
            btnRun.Enabled = true;
            RunningTimetimer.Stop();
        }

        private void tvDirectory_NodeMouseDoubleClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Node.Tag is string path)
                txtPythonScriptPath.Text = path;
        }

        // 鼠标点击节点时发生 
        private void tvDirectory_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            if (e.Node.Tag is string path)
            {
                lbFiles.Items.Clear();
                foreach (string file in Directory.GetFiles(path))
                {
                    var filename = Path.GetFileName(file);
                    if (filename.ToLower().EndsWith(".py"))
                        lbFiles.Items.Add(filename);
                }
                if (lbFiles.Items.Count > 0)
                    lbFiles.SelectedIndex = 0;
            }

            if (tvDirectory.SelectedNode != null && lbFiles.SelectedItems.Count > 0)
            {
                txtPythonScriptPath.Text = tvDirectory.SelectedNode.Tag + "\\" + lbFiles.Items[lbFiles.SelectedIndex];
            }
        }

        private void btnMutilpleFiles_Click(object sender, EventArgs e)
        {
            MultipleFileSelectionForm f1 = new Forms.MultipleFileSelectionForm();
            f1.FilePaths = txtInputFilePath.Text;
            f1.ShowDialog(this);
            txtInputFilePath.Text = f1.FilePaths;
        }

        private void label6_Click(object sender, EventArgs e)
        {

        }

        private void tiRun_Click(object sender, EventArgs e)
        {
            btnRun.PerformClick();
        }

        private void tiStop_Click(object sender, EventArgs e)
        {
            btnStop.PerformClick();
        }

        private void MainForm_KeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.F5: tiRun.PerformClick(); break;
                case Keys.F6: tiStop.PerformClick(); break;
            }
        }

        private void tiOpenDirectory_Click(object sender, EventArgs e)
        {
            tiOpenFolder.PerformClick();
        }

        private void tiSave_Click(object sender, EventArgs e)
        {
            SaveScrypt();
        }

        public string FilepahtToString(string filename)
        {
            return filename.Replace(":", "_").Replace("\\", "_").Replace(".", "_") + ".sav";
        }

        private void SaveScrypt()
        {
            var script = FilepahtToString(txtPythonScriptPath.Text);
            StringBuilder sb = new StringBuilder();
            sb.AppendLine("p:" + txtPythonScriptPath.Text);
            sb.AppendLine("i:" + txtInputFilePath.Text);
            sb.AppendLine("o:" + txtOutputFilePath.Text);
            sb.AppendLine("a:" + "");
            using (StreamWriter sw = new StreamWriter(script, false, Encoding.UTF8))
                sw.WriteLine(sb.ToString());
        }

        public void LoadScript(string scriptpath)
        {
            if (!File.Exists(scriptpath))
                return;
            foreach (var line in File.ReadLines(scriptpath, Encoding.UTF8))
            {
                if (line.Length <= 2) continue;
                if (line.StartsWith("i:"))
                    txtInputFilePath.Text = line.Substring(2);
                if (line.StartsWith("o:"))
                    txtOutputFilePath.Text = line.Substring(2);
            }

            if (cbHistory.Items.Count > 0)
                cbHistory.SelectedIndex = 0;

        }

        private void txtPythonScriptPath_TextChanged(object sender, EventArgs e)
        {
            txtInputFilePath.Clear();
            txtOutputFilePath.Clear();

            tiScriptInfo.PerformClick();
            if (File.Exists(txtPythonScriptPath.Text))
                txtScriptContent.Text = File.ReadAllText(txtPythonScriptPath.Text, Encoding.UTF8);

            var script = FilepahtToString(txtPythonScriptPath.Text);
            LoadScript(script);
            cbHistory.Items.Clear();
            // LoadHistoryCommand(script);

            this.Icon = GetIconFromFile(txtPythonScriptPath.Text);
        }

        /// <summary>
        /// 2024/05/30 测试可用。参考：https://blog.csdn.net/lzl_li/article/details/117038323
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        public static System.Drawing.Icon GetIconFromFile(string fileName)
        {
            if (System.IO.File.Exists(fileName) == false)
                return null;
            return System.Drawing.Icon.ExtractAssociatedIcon(fileName);
        }

        private void tiOpenFolder_Click(object sender, EventArgs e)
        {
            FolderBrowserDialog fbd = new FolderBrowserDialog();
            var default_path = @"c:\gitee.com\data-automation-script-library";
            if (Directory.Exists(default_path))
                fbd.SelectedPath = default_path;
            if (fbd.ShowDialog(this) == DialogResult.OK)
            {
                // 保存 fbd.SelectedPath 到配置文件。
                ca.SetValue("LastPath", fbd.SelectedPath);
                ca.Save();
                FileTreeBuilder.BuildTreeView(tvDirectory, fbd.SelectedPath);
            }
        }

        private void tiRemoveFolder_Click(object sender, EventArgs e)
        {
            if (tvDirectory.SelectedNode != null && tvDirectory.SelectedNode.Parent == null)
            {
                tvDirectory.Nodes.Remove(tvDirectory.SelectedNode);
            }
        }

        private void tiScriptInfo_Click(object sender, EventArgs e)
        {
            txtScriptInfo.Clear();
            txtScriptArg0.Clear();
            txtScriptArg1.Clear();
            txtScriptArg2.Clear();
            txtScriptArg3.Clear();
            tabControl1.SelectedTab = tpScriptInfo;
            string[] info = funs.ReadPyscriptInfo(txtPythonScriptPath.Text);
            if (info != null)
            {
                // MessageBox.Show(string.Join("\r\n", info));
                if (info.Length > 0)
                    txtScriptInfo.Text = info[0];
                if (info.Length > 1)
                    txtScriptArg0.Text = info[1];
                if (info.Length > 2)
                    txtScriptArg1.Text = info[2];
                if (info.Length > 3)
                    txtScriptArg2.Text = info[3];
                if (info.Length > 4)
                    txtScriptArg3.Text = info[4];
            }
        }

        private void tiViewContent_Click(object sender, EventArgs e)
        {
            tabControl1.SelectedTab = tpScriptContent;
            txtScriptContent.SelectionStart = 0;
            txtScriptContent.SelectionLength = 0;
        }

        private void tiAddFolderFromClipboard_Click(object sender, EventArgs e)
        {
            var dir = Clipboard.GetText();
            if (Directory.Exists(dir))
                FileTreeBuilder.BuildTreeView(tvDirectory, dir);
        }

        DateTime startTime = DateTime.Now;
        private void RunningTimetimer_Tick(object sender, EventArgs e)
        {
            var d = DateTime.Now - startTime;
            tiRunningTime.Text = $"Running time: {d.Hours:00}:{d.Minutes:00}:{d.Seconds:00}.{d.Milliseconds:000}";
        }

        private void tvDirectory_AfterSelect(object sender, TreeViewEventArgs e)
        {
            tiRemoveFolder.Enabled = tvDirectory.SelectedNode.Parent == null;
        }

        private void ckRunInScriptDirectory_CheckedChanged(object sender, EventArgs e)
        {
            if (ckRunInScriptDirectory.Checked)
            {
                process.StartInfo.WorkingDirectory = Path.GetDirectoryName(txtPythonScriptPath.Text);
            }
            else
            {
                process.StartInfo.WorkingDirectory = "";
            }
            lblCurrentWorkingDirectory.Text = process.StartInfo.WorkingDirectory;

        }

        private void lbFiles_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (tvDirectory.SelectedNode != null && lbFiles.SelectedItems.Count > 0)
            {
                txtPythonScriptPath.Text = tvDirectory.SelectedNode.Tag + "\\" + lbFiles.Items[lbFiles.SelectedIndex];
            }
        }
    }
}
